﻿using System;
using System.Globalization;
using System.IO;
using System.Net.Mail;
using System.Net.Sockets;
using System.Text;
using log4net;

namespace Sendmail.Server
{
    public class Connection
    {
        #region Variables
        private readonly SmtpServer _smtpServerMaster;
        private readonly int _id;
        private TcpClient _client;

        private string _sender;
        private string[] _receivers;
        private string _subject;
        private string _message;
        #endregion


        #region Constructor
        public Connection(SmtpServer smtpServer, TcpClient client, int id)
        {
            _smtpServerMaster = smtpServer;
            _id = id;
            _client = client;
        }
        #endregion

        #region Logging
        private static readonly ILog Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        public void LogInfo(String text)
        {
            var d = DateTime.Now;
            Log.Info(string.Format("{0:0000}/{1:00}/{2:00} {3:00}:{4:00}:{5:00}.{6:000} - {7}", d.Year, d.Month, d.Day, d.Hour, d.Minute, d.Second, d.Millisecond, text));
        }
        #endregion

        #region Communication
        public void Run(object state)
        {
            LogInfo(string.Format("Client [{0}] connected successfully.", _id));
            LogInfo(string.Format("[{0}] START - Fake proxy server: 220 localhost.", _id));
            var badRequest = 0;
            while (_smtpServerMaster.ServerIsWorking)
            {
                string strMessage;
                try
                {
                    strMessage = Read();
                }
                catch (Exception e)
                {
                    Log.Error(e.Message);
                    break;
                }

                if (strMessage.Length > 0)
                {

                    var requestIsOk = false;

                    #region QUIT
                    if (strMessage.StartsWith("QUIT"))
                    {
                        LogInfo(string.Format("[{0}] Message received: \"QUIT\".", _id));
                        Write("221 Bye");
                        LogInfo(string.Format("[{0}] Send: \"221 Bye\".", _id));
                        CloseClient();
                        LogInfo(string.Format("[{0}] Client close.", _id));
                        break;
                    }
                    #endregion

                    #region EHLO
                    if (strMessage.StartsWith("EHLO"))
                    {
                        requestIsOk = true;
                        LogInfo(string.Format("[{0}] Message received: \"EHLO\".", _id));
                        Write("250 OK");
                        LogInfo(string.Format("[{0}] Send: \"250 OK\"", _id));
                    }
                    #endregion

                    #region RCPT TO
                    if (strMessage.StartsWith("RCPT TO"))
                    {
                        requestIsOk = true;
                        LogInfo(string.Format("[{0}] Message received: \"RCPT TO\".", _id));
                        _receivers = GetStringInBetween("<", ">", strMessage, false, false);
                        Write("250 OK");
                        LogInfo(string.Format("[{0}] Send: \"250 OK\"", _id));
                    }
                    #endregion

                    #region MAIL FROM
                    if (strMessage.StartsWith("MAIL FROM"))
                    {
                        requestIsOk = true;
                        LogInfo(string.Format("[{0}] Message received: \"MAIL FROM\".", _id));
                        var senders = GetStringInBetween("<", ">", strMessage, false, false);
                        _sender = senders[0];
                        Write("250 OK");
                        LogInfo(string.Format("[{0}] Send: \"250 OK\"", _id));
                    }
                    #endregion

                    #region DATA
                    if (strMessage.StartsWith("DATA"))
                    {
                        requestIsOk = true;
                        LogInfo(string.Format("[{0}] Message received: \"DATA\".", _id));
                        Write("354 End data with <CR><LF>.<CR><LF>");
                        LogInfo(string.Format("[{0}] Send: \"354 End data with <CR><LF>.<CR><LF>\"", _id));
                        strMessage = "";
                        while (true)
                        {
                            try
                            {
                                var tempStrMessag = Read();

                                #region Subject
                                if (tempStrMessag.Contains("Subject:"))
                                {
                                    _subject = tempStrMessag.Substring(9, tempStrMessag.IndexOf("\n", StringComparison.Ordinal)-10);
                                    var index = tempStrMessag.IndexOf("\n", StringComparison.Ordinal) + 1;
                                    if (index < tempStrMessag.Length)
                                    {
                                        tempStrMessag = tempStrMessag.Substring(index);
                                    }
                                    else
                                    {
                                        continue;
                                    }
                                }
                                #endregion

                                #region From
                                if (tempStrMessag.Contains("From:"))
                                {
                                    var index = tempStrMessag.IndexOf('>') + 1;
                                    if (index < tempStrMessag.Length)
                                    {
                                        tempStrMessag = tempStrMessag.Substring(index);
                                    }
                                    else
                                    {
                                        continue;
                                    }
                                }
                                #endregion

                                #region To
                                if (tempStrMessag.Contains("To:"))
                                {
                                    var index = tempStrMessag.IndexOf('>') + 1;
                                    if (index < tempStrMessag.Length)
                                    {
                                        tempStrMessag = tempStrMessag.Substring(index);
                                    }
                                    else
                                    {
                                        continue;
                                    }
                                }
                                #endregion

                                #region End
                                if (tempStrMessag.Contains("\r\n.\r\n"))
                                {
                                    strMessage += tempStrMessag.Substring(0, tempStrMessag.IndexOf("\r\n.\r\n", StringComparison.Ordinal));
                                    break;
                                }
                                #endregion

                                strMessage += tempStrMessag;
                            }
                            catch (Exception e)
                            {
                                Log.Error(e.Message);
                                break;
                            }
                        }
                        _message = strMessage;
                        LogInfo(string.Format("[{0}] Message received: \"{1}\".", _id, _message));
                        SaveMail();
                        Write("250 OK");
                        LogInfo(string.Format("[{0}] Send: \"250 OK\"", _id));
                    }
                    #endregion


                    #region BadRequest
                    if (!requestIsOk)
                    {
                        badRequest++;
                        LogInfo(string.Format("[{0}] Bad request {1}.", _id, badRequest));
                        if (badRequest > _smtpServerMaster.BadRequestMax)
                        {
                            LogInfo(string.Format("[{0}] Too many bad requests.", _id));
                            CloseClient();
                            break;
                        }
                    }
                    #endregion
                }
            }
            CloseClient();
        }

        private void CloseClient()
        {

            if (_client != null)
            {
                _client.Close();
                _client = null;
            }
        }

        private void Write(String strMessage)
        {
            var clientStream = _client.GetStream();
            var encoder = new ASCIIEncoding();
            var buffer = encoder.GetBytes(strMessage);
            clientStream.Write(buffer, 0, buffer.Length);
            clientStream.Flush();
        }

        private String Read()
        {
            var clientStream = _client.GetStream();
            var messageBytes = new byte[8192];
            var encoder = new ASCIIEncoding();
            var bytesRead = clientStream.Read(messageBytes, 0, 8192);
            return encoder.GetString(messageBytes, 0, bytesRead);
        }

        public static string[] GetStringInBetween(string strBegin, string strEnd, string strSource, bool includeBegin, bool includeEnd)
        {
            string[] result = { "", "" };
            var iIndexOfBegin = strSource.IndexOf(strBegin, StringComparison.Ordinal);
            if (iIndexOfBegin != -1)
            {
                if (includeBegin)
                {
                    iIndexOfBegin -= strBegin.Length;
                }
                strSource = strSource.Substring(iIndexOfBegin + strBegin.Length);
                var iEnd = strSource.IndexOf(strEnd, StringComparison.Ordinal);
                if (iEnd != -1)
                {
                    if (includeEnd)
                    {
                        iEnd += strEnd.Length;
                    }
                    result[0] = strSource.Substring(0, iEnd);
                    if (iEnd + strEnd.Length < strSource.Length)
                    {
                        result[1] = strSource.Substring(iEnd + strEnd.Length);
                    }
                }
            }
            else
            {
                result[1] = strSource;
            }
            return result;
        }
        #endregion

        #region Mail Saving
        private void SaveMail()
        {
            if (_sender != null && _receivers != null)
            {
                if (_subject == null) _subject = "";
                if (_message == null) _message = "";
                var mail = new MailMessage { Subject = _subject, Body = _message, From = new MailAddress(_sender) };

                foreach (var receiver in _receivers)
                {
                    if (!String.IsNullOrEmpty(receiver))
                    {
                        try
                        {
                            mail.To.Add(new MailAddress(receiver));
                        }
                        catch(FormatException){}
                    }
                }

                foreach (var receiver in _receivers)
                {
                    var index = receiver.IndexOf("@", StringComparison.Ordinal);
                    if(index > 0)
                    {
                        var newReceiver = receiver.Substring(0, index);
                        var path1 = string.Format("{0}\\mailbox\\{1}", Environment.CurrentDirectory.ToString(CultureInfo.InvariantCulture), newReceiver);
                        if (Directory.Exists(path1))
                        {
                            var path2 = string.Format("{0}\\mailbox\\{1}\\{2}.eml",Environment.CurrentDirectory.ToString(CultureInfo.InvariantCulture),newReceiver, _smtpServerMaster.CurrentMailId);
                            mail.Save(path2);
                            LogInfo(string.Format("[{0}] Message is saved in {1}.", _id, path2));
                        }
                    }
                }
            }
        }
        #endregion

    }
}
